package com.ehome.poct.core.dao.mybatis;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ehome.mybatisplus.support.AbstractCrudRepository;
import com.ehome.poct.core.api.common.StripTypeConst;
import com.ehome.poct.base.api.enums.StripTypeEnums;
import com.ehome.poct.core.api.dto.ReportListDTO;
import com.ehome.poct.core.api.dto.TestDataDTO;
import com.ehome.poct.core.api.dto.WxDTO;
import com.ehome.poct.core.api.dto.check.CheckItemDTO;
import com.ehome.poct.core.api.dto.check.CheckReportDTO;
import com.ehome.poct.core.api.dto.check.gold.TestConfPoctStripTypeDTO;
import com.ehome.poct.core.api.dto.check.gold.TestReportPoctDTO;
import com.ehome.poct.core.api.dto.check.gold.TestReportPoctUniDTO;
import com.ehome.poct.core.api.dto.checkdata.CheckItemResultDTO;
import com.ehome.poct.core.api.dto.common.AgeSegment;
import com.ehome.poct.core.api.dto.common.CommonUtil;
import com.ehome.poct.core.api.dto.patient.PatientCategory;
import com.ehome.poct.core.api.dto.report.BloodPreciseJudeDTO;
import com.ehome.poct.core.api.dto.report.TestConfUniProjectDescDTO;
import com.ehome.poct.core.api.dto.report.TestReportConfigDTO;
import com.ehome.poct.core.api.vo.CheckReportVO;
import com.ehome.poct.core.api.vo.CheckResultVO;
import com.ehome.poct.core.api.vo.ReportDetailVO;
import com.ehome.poct.core.api.vo.ReportDtlVO;
import com.ehome.poct.core.api.vo.patient.PatientDetailVO;
import com.ehome.poct.core.dao.entity.check.*;
import com.ehome.poct.core.dao.entity.order.TestOrderDO;
import com.ehome.poct.core.dao.mapping.CheckReportMapping;
import com.ehome.poct.core.dao.mapping.PatientMapping;
import com.ehome.poct.core.dao.mybatis.mapper.*;
import com.ehome.poct.core.dao.mybatis.mapper.order.TestOrderMapper;
import com.ehome.poct.core.repository.BldReportRepository;
import com.ehome.poct.core.repository.CheckItemIndicatorRepository;
import com.ehome.poct.core.repository.CheckProjectRepository;
import com.ehome.poct.core.repository.PatientRepository;
import com.ehome.poct.core.repository.check.CheckItemCategoryRepository;
import com.ehome.poct.core.repository.report.BloodPreciseJudeRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ehome.poct.core.api.common.ValueConst.VAL_5;

/**
 * @description:
 * @author: muqiu
 * @time: 2022/3/3 17:56
 */
@Slf4j
@Repository
@RequiredArgsConstructor
public class BldReportRepositoryImpl extends AbstractCrudRepository<CheckReportDO, CheckReportDTO> implements BldReportRepository {
    private final CheckItemCategoryRepository checkItemCategoryRepository;
    private final CheckItemIndicatorRepository checkItemIndicatorRepository;
    private final CheckProjectRepository checkProjectRepository;

    private final BldReportMapper bldReportMapper;

    private final CheckReportMapping checkReportMapping;

    private final CheckItemIndicatorMapper checkItemIndicatorMapper;

    private final CheckDataMapper checkDataMapper;

    private final CheckProjectMapper projectMapper;

    private final TestOrderMapper orderMapper;

    private final PatientRepository patientRepository;

    private final PatientMapping patientMapping;

    private final TestReportPoctMapper reportSigMapper;

    private final TestDataDetailMapper dataDetailMapper;

    private final TestConfPoctStripTypeMapper confPoctStripTypeMapper;

    private final TestConfPoctAnalysisMapper jbyAnalysisMapper;

    private final TestConfPoctStripMappingMapper stripMappingMapper;

    private final TestReportPoctUniMapper testReportPoctUniMapper;

    private final TestConfBloodPoctAnalysisMapper analysisMapper;
    private final BloodPreciseJudeRepository preciseJudeRepository;


    private final RedisTemplate redisTemplate;

    private final WxDTO wxProperties;

    @Value("${notify.detail.env}") private String enyType;

    public static final String WEXIN_TOKEN_KEY = "wexin_access_token";
    private final Integer CRP = 4;




    @Override
    public com.ehome.core.page.Page<CheckReportVO> getCheckList(ReportListDTO dto) {
        final var reportPage = reportSigMapper.selectPage(new Page<>(dto.getPageIndex(),dto.getPageSize()),
                Wrappers.<TestReportPoctDO>lambdaQuery()
                        .eq(TestReportPoctDO::getClinicId, dto.getClinicId())
                        .eq(TestReportPoctDO::getPayStatus, 20)
                        .le(ObjectUtil.isNotEmpty(dto.getEndTime()), TestReportPoctDO::getCreateTime, dto.getEndTime())
                        .ge(ObjectUtil.isNotEmpty(dto.getStartTime()), TestReportPoctDO::getCreateTime, dto.getStartTime())
                        .orderByDesc(TestReportPoctDO::getCreateTime));
        com.ehome.core.page.Page<CheckReportVO> page = new com.ehome.core.page.Page<>();
        page.setCurrent((int) reportPage.getCurrent());
        page.setSize((int) reportPage.getSize());
        page.setTotal((int)reportPage.getTotal());
        page.setList(checkReportMapping.toReportListVO(reportPage.getRecords()));
        return page;
    }

    @Override
    public ReportDetailVO getCheckReportByOrderNo(String orderNo) {
        return getDetail(bldReportMapper.selectOne(Wrappers.<CheckReportDO>lambdaQuery().eq(CheckReportDO::getOrderNo, orderNo)));
    }

    @Override
    public ReportDetailVO getCheckReport(Long reportId) {
        return getDetail(bldReportMapper.selectById(reportId));
    }


    @Override
    public void delBld(Long id) {
        bldReportMapper.del(id,LocalDateTime.now());
    }

    /**
     * 获取详情
     *
     * @param report
     * @return
     */
    private ReportDetailVO getDetail(CheckReportDO report) {
        // 单机检测
        final var data = checkDataMapper.selectById(report.getBloodCheckDataId());
        final var project = projectMapper.selectOne(Wrappers.<CheckProjectDO>lambdaQuery()
                .eq(CheckProjectDO::getCheckProjectCode, report.getProjectCode()));
        final var order = orderMapper.selectOne(Wrappers.<TestOrderDO>lambdaQuery().eq(TestOrderDO::getTestOrderNo, report.getOrderNo()));
        final var patient = patientRepository.findById(report.getPatientId());
        final var bldPatientType = this.
                toAgeInt(patient.getDetail().getCategory(AgeSegment.ADULT_YEAR_STANDARD_BLD));

        var pushLink = this.generateNotifyUrl(report.getBloodCheckReportId(), 2);
        final var kpiMap = checkItemIndicatorMapper.findByType(bldPatientType)
                .stream().collect(Collectors.toMap(CheckItemIndicatorDO::getSimpleName, Function.identity()));
        final var detail = checkReportMapping.toVO(report, data, patientMapping.to(patient), project, order);
        detail.getResults().forEach(r -> checkReportMapping.to(r, kpiMap.get(r.getType())));
         var jbySigReports = reportSigMapper.selectList(Wrappers.<TestReportPoctDO>lambdaQuery().eq(TestReportPoctDO::getPayOrderNo, report.getOrderNo()).orderByAsc(TestReportPoctDO::getStripTypeCode));
        jbySigReports = jbySigReports.stream().filter(r -> r.getStripTypeCode() == 4 || r.getStripTypeCode() == 5).sorted(Comparator.comparing(TestReportPoctDO::getStripTypeCode)).collect(Collectors.toList());
        // 联机检测
        if (CollUtil.isNotEmpty(jbySigReports)) {
              var jbyResults = jbySigReports.stream().map(r -> {

                final var jbyData = dataDetailMapper.selectById(r.getTestPoctDataId());
                final var strip = confPoctStripTypeMapper.selectOne(Wrappers.<TestConfPoctStripTypeDO>lambdaQuery().eq(TestConfPoctStripTypeDO::getTypeCode, r.getStripTypeCode()));
                final var analysis = jbyAnalysisMapper.selectOne(Wrappers.<TestConfPoctAnalysisDO>lambdaQuery().eq(TestConfPoctAnalysisDO::getConfPoctAnalysisId, r.getAnalyseId()));
                final var stripMapping = stripMappingMapper.selectOne(Wrappers.<TestConfPoctStripMappingDO>lambdaQuery()
                        .eq(TestConfPoctStripMappingDO::getStripTypeCode, jbyData.getStripTypeCode())
                        .eq(TestConfPoctStripMappingDO::getStripBatch, jbyData.getStripBatch()));
                return checkReportMapping.to(jbyData, strip, stripMapping, analysis, strip.getReference());
            }).collect(Collectors.toList());

            final var jbyUniReport = testReportPoctUniMapper.selectOne(Wrappers.<TestReportPoctUniDO>lambdaQuery()
                    .eq(TestReportPoctUniDO::getPayOrderNo, report.getOrderNo())
                    .eq(TestReportPoctUniDO::getStripTypeCodeA, StripTypeEnums.CRP.getStripTypeCode())
                    .eq(TestReportPoctUniDO::getStripTypeCodeB, StripTypeEnums.SAA.getStripTypeCode())
            );

            // 获取联合解析内容
            final var analysis = analysisMapper.selectOne(Wrappers.<TestConfBloodPoctAnalysisDO>lambdaQuery()
                    .eq(TestConfBloodPoctAnalysisDO::getBloodPoctUniAnalysisCode,
                            jbyUniReport.getResultCode() + StrUtil.replace(report.getWbcResultCode(), 5, 6, '0')));
            // 获取病症关联
//            final var diseaseCategory = this.getDiseaseCategory(checkReportMapping.to(jbyUniReport));
            pushLink = this.generateNotifyUrl(jbyUniReport.getTestReportUniPoctId(), 4);
            jbyResults = reParesCrpRows(jbyResults);
            detail.getResults().addAll(jbyResults);
//            detail.setDiseaseCategory(checkReportMapping.to(diseaseCategory));
            Optional.ofNullable(analysis).ifPresent(a -> detail.setAnalysis(a.getUnionAnalysis()));
        }
        detail.setPushLink(pushLink);
        return detail;
    }

    /**
     * 根据报告生成推送二维码
     *
     * @param reportId
     * @param type
     * @return
     */
    public String generateNotifyUrl(Long reportId, Integer type) {
        return getWxUrlWithExpire(30 * 24 * 3600, reportId, String.valueOf(type), enyType);
    }

    /**
     * 获取微信推送二维码链接
     *
     * @param expire_seconds
     * @param reportId
     * @param reportType
     * @param envirType
     * @return
     */
    public String getWxUrlWithExpire(int expire_seconds, Long reportId, String reportType, String envirType){
        var accessToken = getAccessToken();
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token="+accessToken;
        JSONObject body = new JSONObject();
        body.set("expire_seconds",expire_seconds);
        body.set("action_name", "QR_STR_SCENE");
        JSONObject sid = new JSONObject();
        sid.set("scene_str",reportType+envirType+reportId);
        JSONObject scene = new JSONObject();
        scene.set("scene",sid);
        body.set("action_info",scene);

        JSONObject result = JSONUtil.parseObj(HttpUtil.post(url, body.toString()));
        var qrUrl = result.get("url");
        if (qrUrl == null) {
            // 获取失败，token过期，删除缓存中的token
            if (result.get("errcode") != null) {
                redisTemplate.delete(WEXIN_TOKEN_KEY);
            }
            log.error("推送二维码获取失败：微信返回结果：{}" + result);
            return null;
        }
        return qrUrl.toString();
    }
    /**
     *
     *
     * @param category
     * @return 1:儿童 2：成人男 3：成人女
     */
    public int toAgeInt(PatientCategory category) {
        if (category.equals(PatientCategory.ADULT_MALE)) {
            return 2;
        } else if(category.equals(PatientCategory.ADULT_FEMALE)) {
            return 3;
        }else if(category.equals(PatientCategory.CHILD_MALE) || category.equals(PatientCategory.CHILD_FEMALE)) {
            return 1;
        }
        return 2;
    }

    /**
     * 获取微信授权
     *
     * @return
     */
    public String getAccessToken(){
        String accessToken = (String) redisTemplate.opsForValue().get(WEXIN_TOKEN_KEY);
        if(accessToken == null){
            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
                    + wxProperties.getAppId() + "&secret=" + wxProperties.getSecret();
            JSONObject result = JSONUtil.parseObj(HttpUtil.get(url));
            var tokenObj = result.get("access_token");
            if (ObjectUtil.isNull(tokenObj)) {
                log.error("微信Token获取失败, 微信返回结果:{}", tokenObj);
                return "";
            }
            accessToken = tokenObj.toString();
            redisTemplate.opsForValue().set(WEXIN_TOKEN_KEY, accessToken, 60, TimeUnit.MINUTES);
            refreshOldToken(accessToken);
        }
        return accessToken;
    }

    /**
     * 刷新老系统的token
     *
     * @param token
     */
    private void refreshOldToken(String token) {
        var url = wxProperties.getRefreshUrl() + "?token=" + token + "&md5=" + MD5.create().digestHex(token + "000390c152af4c489d158093b74ec4ac");
        try {
            var result = HttpUtil.get(url, 2000);
            log.info("更新.NET服务Token结果", result);
        } catch (Exception e) {
            log.error("更新.NET服务微信Token异常，原因：{}", e);
        }
    }

    @Override
    public List<List<Object>> getResultWithKpis(TestDataDTO data, String result, Integer dataType, Integer isChild) {
        if (Validator.isNull(data)) {
            return null;
        }
        var typeCode = data.getStripTypeCode();
        if (data.isOldData()) {
            typeCode += 200;
        }
        // 检测项目
        TestConfPoctStripTypeDTO stripType = data.getStripType();
        // 1. 获取项目指标
        List<TestConfPoctAnalysisDO> kpis = new TestConfPoctAnalysisDO().selectList(Wrappers.<TestConfPoctAnalysisDO>lambdaQuery()
                .eq(TestConfPoctAnalysisDO::getStripTypeCode, typeCode)
                .eq(TestConfPoctAnalysisDO::getStripBatch, data.getStripBatch())
                .eq(TestConfPoctAnalysisDO::getPatientType,isChild)
                .orderByAsc(TestConfPoctAnalysisDO::getMin));
        // 2. 通过子项目名称进行分组
        Map<String, List<TestConfPoctAnalysisDO>> map = kpis.stream()
                .peek(kpi -> {
                    // 没有子项目则将子项目名称设为检测项目名称
                    if (StrUtil.isEmpty(kpi.getSubName())) {
                        kpi.setSubName(stripType.getName());
                    }
                }).sorted(Comparator.comparing(TestConfPoctAnalysisDO::getSubCode))
                .collect(Collectors.groupingBy(TestConfPoctAnalysisDO::getSubName, LinkedHashMap:: new, Collectors.toList()));
        // 3. 将子项目转换为检测结果
        AtomicInteger order = new AtomicInteger(0);
        if(data.getStripTypeCode().equals(StripTypeConst.CRP)){
            return  paresCrpRows(kpis,order,data,result,dataType,isChild,stripType);
        }else {
            return map.entrySet().stream().map(k -> new Row(order.incrementAndGet(), result.split("/"))
                    .paresToColumns(data, dataType, stripType, k, isChild)).collect(Collectors.toList());
        }
    }

    @Override
    public ReportDtlVO toReportDtlVO(TestReportPoctDTO r, PatientDetailVO patient, TestDataDTO data, String generateNotifyUrl) {
        return checkReportMapping.toReportDtlVO(r,patient,data,generateNotifyUrl);
    }

    @Override
    public ReportDtlVO toReportDtlVO(TestReportPoctUniDTO r, String stripName, PatientDetailVO patient, String generateNotifyUrl, TestConfUniProjectDescDTO projectDesc, String sampleType) {
        return checkReportMapping.to(r, stripName, patient, generateNotifyUrl, projectDesc,sampleType);
    }

    @Override
    public void delByOrderNo(String orderNo) {
        bldReportMapper.update(null, Wrappers.<CheckReportDO>lambdaUpdate()
                .eq(CheckReportDO::getOrderNo, orderNo)
                .set(CheckReportDO::getPayStatus, 30)
                .set(CheckReportDO::getUpdateTime, LocalDateTime.now()));
//        bldReportMapper.delete(Wrappers.<CheckReportDO>lambdaQuery().eq(CheckReportDO::getOrderNo, orderNo));
    }

    @Override
    public void updateResultCode(CheckReportDTO report, List<CheckItemResultDTO> results, Integer type, Integer projectCode) {
        final var categories = checkItemCategoryRepository.findByProjectCode(projectCode);
        final var resultMap = results.stream().collect(Collectors.toMap(CheckItemResultDTO::getType, CheckItemResultDTO::getValue));
        final var indicators = checkItemIndicatorRepository.selectByParam(projectCode, type);
        final var indicatorMap = indicators.stream().collect(Collectors.toMap(CheckItemDTO::getItemName, Function.identity()));
        final var preciseJudgeMap = preciseJudeRepository.list(new BloodPreciseJudeDTO()).stream()
                .collect(Collectors.groupingBy(BloodPreciseJudeDTO::getCheckProjectItem));
        final var codeMap = categories.stream().collect(HashMap<String, String>::new, (map, c) -> {
            final var code = c.getItems().stream().map(i -> {
                final var value = resultMap.get(i.getCheckTypeEnname());
                final var indicator = indicatorMap.get(i.getCheckTypeEnname());
                // 一些特殊条目需要更详细的code判断
                final var preciseJudgeList = preciseJudgeMap.get(i.getCheckTypeEnname());
                if (CollUtil.isNotEmpty(preciseJudgeList)) {
                    final var judge = preciseJudgeList.stream()
                            .filter(it -> value.compareTo(it.getMin()) > 0 && value.compareTo(it.getMax()) <= 0)
                            .findFirst().map(BloodPreciseJudeDTO::getJudge).orElse(null);
                    if (StrUtil.isNotBlank(judge)) {
                        return judge;
                    }
                }
                if (indicator.getMaxDanger() != null && value.compareTo(indicator.getMaxDanger()) > 0) {
                    return "5";
                } else if (indicator.getMinDanger() != null && value.compareTo(indicator.getMinDanger()) < 0) {
                    return "1";
                } else if (value.compareTo(indicator.getMaxValue()) > 0) {
                    return "4";
                } else if (value.compareTo(indicator.getMinValue()) > 0) {
                    return "3";
                }
                return "2";
            }).collect(Collectors.joining());
            map.put(c.getAbbreviation(), code);
        }, HashMap::putAll);
        final var updateReport = new CheckReportDTO().setWbcResultCode(codeMap.get("wbc") + "000000")
                .setHgbResultCode("000000" + codeMap.get("hgb") + "0000")
                .setRdwResultCode("000000" + codeMap.get("rdw") + "0")
                .setHctResultCode("0000000000" + codeMap.get("hct") + "0")
                .setPltResultCode("00000000000" + codeMap.get("plt"));
        this.update(report.getBloodCheckReportId(), updateReport);
    }

    @Override
    public void reportConfigBld(TestReportConfigDTO testReportConfigDTO) {
        if(testReportConfigDTO.getDisabled() == 0){
            bldReportMapper.reportConfigBld(testReportConfigDTO);
            bldReportMapper.delete(Wrappers.<CheckReportDO>lambdaQuery().eq(CheckReportDO::getClinicId,testReportConfigDTO.getClinicId())
                    .ge(testReportConfigDTO.getStartTime() != null,CheckReportDO::getCreateTime,testReportConfigDTO.getStartTime())
                    .le(testReportConfigDTO.getEndTime() != null,CheckReportDO::getCreateTime,testReportConfigDTO.getEndTime()));
        }else {
            bldReportMapper.reportConfigBld(testReportConfigDTO);
        }
    }

    @Override
    public void updateByOrderNo(String orderNo) {
        bldReportMapper.updateByOrderNo(orderNo);
    }

    /**
     * 构建CRP 特殊的两行 超敏和常规分成两行
     * @param kpis 分析标准集合
     * @param order 顺序
     * @param data 检测数据
     * @param result 检测结果
     * @param dataType 参数类型 2是打印数据
     * @param isChild 儿童标准
     * @param stripType 检测类型
     * @return 2行 CRP 超敏 crp 常规
     */
    private List<List<Object>> paresCrpRows( List<TestConfPoctAnalysisDO> kpis , AtomicInteger order,TestDataDTO data, String result, Integer dataType,Integer isChild,  TestConfPoctStripTypeDTO stripType ){
        List<List<Object>> resultRows = new ArrayList<>();
        BigDecimal value  = data.getValue1();
        String valueHtml = CommonUtil.translateTestData(data, value,isChild);
        String[] superAndNormal ;
        if (dataType == 2) {
            superAndNormal = CommonUtil.processCrpSaaSuperNoHtml( valueHtml.replaceAll("<br/>", "\n"));
        }else
        {
            superAndNormal = CommonUtil.processCrpSaaSuper(CommonUtil.createHtmlTag(kpis.stream().filter(a -> value.compareTo(a.getMin()) > 0 && value.compareTo(a.getMax()) <= 0).findFirst().map(TestConfPoctAnalysisDO::getColor).orElse(""), valueHtml));
        }
        resultRows.add(new Row(order.incrementAndGet(), result.split("/")).paresToColumnsCRP("C反应蛋白",dataType,stripType,superAndNormal[0],"CRP检测"));
        resultRows.add(new Row(order.incrementAndGet(), result.split("/")).paresToColumnsCRP("超敏C反应蛋白",dataType,stripType,superAndNormal[1],"hs-CRP检测"));
        return  resultRows;
    }

    private List<CheckResultVO> reParesCrpRows(List<CheckResultVO> originRows){
        String[] crpSuperAndNormal = CommonUtil.processCrpSaaSuperNoHtml( originRows.stream().filter(i->CRP.equals(i.getStripTypeCode())).findFirst().map(CheckResultVO::getValue).orElse(""));
        CheckResultVO crp=originRows.stream().filter(i-> CRP.equals( i.getStripTypeCode())).findFirst().get();
        CheckResultVO crpNormal = new CheckResultVO(crp).setValue(crpSuperAndNormal[0]).setTypeName("C反应蛋白").setType("CRP检测");
        CheckResultVO crpSuper = new CheckResultVO(crp).setValue(crpSuperAndNormal[1]).setTypeName("超敏C反应蛋白").setType("hs-CRP检测");
        originRows.removeIf(i->CRP.equals( i.getStripTypeCode()));
        originRows.add(crpNormal);
        originRows.add(crpSuper);
        return  originRows.stream().sorted(Comparator.comparing(CheckResultVO::getStripTypeCode)).peek(i->{if(VAL_5.equals(i.getStripTypeCode())){ i.setType("SAA检测"); }}).collect(Collectors.toList());
    }

}
